package main


import (
	"log"
	"time"
	"os"
	"strings"
	"regexp"
	"io/ioutil"
    "path/filepath"
	"github.com/gin-gonic/gin"
	"encoding/json"
	"strconv"
	"myfile/mymongo"
	"myfile/myfiles"
	"myfile/myffmpeg"
)

type Mydata struct {
	Name  	string
	Id 		string
}

type Mypage struct {
	Limit   int64
	Page    int64
	Search  string
}


var MPATH string

const (
	StatusContinue           = 100 // RFC 9110, 15.2.1
	StatusSwitchingProtocols = 101 // RFC 9110, 15.2.2
	StatusProcessing         = 102 // RFC 2518, 10.1
	StatusEarlyHints         = 103 // RFC 8297

	StatusOK                   = 200 // RFC 9110, 15.3.1
	StatusCreated              = 201 // RFC 9110, 15.3.2
	StatusAccepted             = 202 // RFC 9110, 15.3.3
	StatusNonAuthoritativeInfo = 203 // RFC 9110, 15.3.4
	StatusNoContent            = 204 // RFC 9110, 15.3.5
	StatusResetContent         = 205 // RFC 9110, 15.3.6
	StatusPartialContent       = 206 // RFC 9110, 15.3.7
	StatusMultiStatus          = 207 // RFC 4918, 11.1
	StatusAlreadyReported      = 208 // RFC 5842, 7.1
	StatusIMUsed               = 226 // RFC 3229, 10.4.1

	StatusMultipleChoices  = 300 // RFC 9110, 15.4.1
	StatusMovedPermanently = 301 // RFC 9110, 15.4.2
	StatusFound            = 302 // RFC 9110, 15.4.3
	StatusSeeOther         = 303 // RFC 9110, 15.4.4
	StatusNotModified      = 304 // RFC 9110, 15.4.5
	StatusUseProxy         = 305 // RFC 9110, 15.4.6

	StatusTemporaryRedirect = 307 // RFC 9110, 15.4.8
	StatusPermanentRedirect = 308 // RFC 9110, 15.4.9

	StatusBadRequest                   = 400 // RFC 9110, 15.5.1
	StatusUnauthorized                 = 401 // RFC 9110, 15.5.2
	StatusPaymentRequired              = 402 // RFC 9110, 15.5.3
	StatusForbidden                    = 403 // RFC 9110, 15.5.4
	StatusNotFound                     = 404 // RFC 9110, 15.5.5
	StatusMethodNotAllowed             = 405 // RFC 9110, 15.5.6
	StatusNotAcceptable                = 406 // RFC 9110, 15.5.7
	StatusProxyAuthRequired            = 407 // RFC 9110, 15.5.8
	StatusRequestTimeout               = 408 // RFC 9110, 15.5.9
	StatusConflict                     = 409 // RFC 9110, 15.5.10
	StatusGone                         = 410 // RFC 9110, 15.5.11
	StatusLengthRequired               = 411 // RFC 9110, 15.5.12
	StatusPreconditionFailed           = 412 // RFC 9110, 15.5.13
	StatusRequestEntityTooLarge        = 413 // RFC 9110, 15.5.14
	StatusRequestURITooLong            = 414 // RFC 9110, 15.5.15
	StatusUnsupportedMediaType         = 415 // RFC 9110, 15.5.16
	StatusRequestedRangeNotSatisfiable = 416 // RFC 9110, 15.5.17
	StatusExpectationFailed            = 417 // RFC 9110, 15.5.18
	StatusTeapot                       = 418 // RFC 9110, 15.5.19 (Unused)
	StatusMisdirectedRequest           = 421 // RFC 9110, 15.5.20
	StatusUnprocessableEntity          = 422 // RFC 9110, 15.5.21
	StatusLocked                       = 423 // RFC 4918, 11.3
	StatusFailedDependency             = 424 // RFC 4918, 11.4
	StatusTooEarly                     = 425 // RFC 8470, 5.2.
	StatusUpgradeRequired              = 426 // RFC 9110, 15.5.22
	StatusPreconditionRequired         = 428 // RFC 6585, 3
	StatusTooManyRequests              = 429 // RFC 6585, 4
	StatusRequestHeaderFieldsTooLarge  = 431 // RFC 6585, 5
	StatusUnavailableForLegalReasons   = 451 // RFC 7725, 3

	StatusInternalServerError           = 500 // RFC 9110, 15.6.1
	StatusNotImplemented                = 501 // RFC 9110, 15.6.2
	StatusBadGateway                    = 502 // RFC 9110, 15.6.3
	StatusServiceUnavailable            = 503 // RFC 9110, 15.6.4
	StatusGatewayTimeout                = 504 // RFC 9110, 15.6.5
	StatusHTTPVersionNotSupported       = 505 // RFC 9110, 15.6.6
	StatusVariantAlsoNegotiates         = 506 // RFC 2295, 8.1
	StatusInsufficientStorage           = 507 // RFC 4918, 11.5
	StatusLoopDetected                  = 508 // RFC 5842, 7.2
	StatusNotExtended                   = 510 // RFC 2774, 7
	StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6
)

func init() {
	ex, err := os.Executable()
    if err != nil {
        panic(err)
    }
    mpath := filepath.Dir(ex)
    MPATH = filepath.Join(mpath, "download")
    if myfiles.Exists(MPATH) == false || myfiles.IsDir(MPATH) == false {
    	os.Mkdir(MPATH, os.ModePerm)
    }
    //MPATH = "F:\\go-server\\mygo\\mygin_mongo\\download"
    log.Println("init", MPATH)
}

func main() {
	db, err := mymongo.InitDB("myfile", "data", "dm", "pl")
	if err != nil {
		log.Println("数据库初始化失败")
		panic(err) 
	}
	var _min = int64(1)
	var _max = int64(1000)
	gin.ForceConsoleColor()
	r := gin.Default()
	log.Println(_min,_max, MPATH)
	r.MaxMultipartMemory = _min << _max
	//get 请求
	r.Use(Authorize())
	r.GET("/api/init", func(cont *gin.Context) {MyInItObj(cont, db, "GET")})
	r.GET("/api/append", func(cont *gin.Context) {MyInsert(cont, db, "GET")})
	r.GET("/api/delete", func(cont *gin.Context) {MyDelete(cont, db, "GET")})
	r.GET("/api/update", func(cont *gin.Context) {MyUpdate(cont, db, "GET")})
	r.GET("/api/check",  func(cont *gin.Context) {MyFindAll(cont, db, "GET")})
	r.GET("/api/watch",  func(cont *gin.Context) {MyFindOne(cont, db, "GET")})
	r.GET("/api/download", func(cont *gin.Context) {MyDownload(cont, db, "GET")})
	r.GET("/api/barrage_comments/v3/", func(cont *gin.Context) {MyBarrage_comments(cont, db, "GET")})
	r.GET("/api/subtitle_lyrics/v2/", func(cont *gin.Context) {MySubtitle_lyrics(cont, db, "GET")})
	r.GET("/api/cover/v3/", func(cont *gin.Context) {MyCoverv3(cont, db, "GET")})
	r.GET("/api/user", func(cont *gin.Context) {MyUser(cont, db, "GET")})
	r.GET("/api/refresh", func(cont *gin.Context) {Myrefresh(cont, db, "GET")})
	r.GET("/api/ffmpeg/v1/", func(cont *gin.Context) {Myffmpeg_conversion(cont, db, "GET")})
	//post请求
	r.POST("/api/append", func(cont *gin.Context) {MyInsert(cont, db, "POST")})
	r.POST("/api/delete", func(cont *gin.Context) {MyDelete(cont, db, "POST")})
	r.POST("/api/update", func(cont *gin.Context) {MyUpdate(cont, db, "POST")})
	r.POST("/api/check",  func(cont *gin.Context) {MyFindAll(cont, db, "POST")})
	r.POST("/api/watch",  func(cont *gin.Context) {MyFindOne(cont, db, "POST")})
	r.POST("/api/download", func(cont *gin.Context) {MyDownload(cont, db, "POST")})
	r.POST("/api/barrage_comments/v3/", func(cont *gin.Context) {MyBarrage_comments(cont, db, "POST")})
	r.POST("/api/subtitle_lyrics/v2/", func(cont *gin.Context) {MySubtitle_lyrics(cont, db, "POST")})
	r.POST("/api/cover/v3/", func(cont *gin.Context) {MyCoverv3(cont, db, "POST")})
	r.POST("/api/ffmpeg/v1/", func(cont *gin.Context) {Myffmpeg_conversion(cont, db, "POST")})
	r.POST("/api/update_barrage_comments/", func(cont *gin.Context) {MyUpdateBarrage_comments(cont, db, "POST")})
	r.Run(":8080")
}

func MyInItObj(cont *gin.Context, client *mymongo.Myclient, methods string) {
	client.MyInItDB()
	os.RemoveAll(MPATH)
	os.Mkdir(MPATH, os.ModePerm)
	cont.JSON(StatusOK, gin.H{"code":1})
}


func MyInsert(cont *gin.Context, client *mymongo.Myclient, methods string) {
	file, _ := cont.FormFile("file")
	_time := strings.Split(time.Now().String(), ".")[0]
	id := strconv.FormatInt(time.Now().UnixNano(), 10)
	_, ret := client.FindOne(id)
	if ret.Code == 0 {
		_type, suffx := myfiles.FileSuffix(file.Filename)
		ret = client.Insert(id, _type, file.Filename, _time, suffx)
		if ret.Code == 1 {
			os.Mkdir(filepath.Join(MPATH, id), os.ModePerm)
			cont.SaveUploadedFile(file, filepath.Join(MPATH, id, file.Filename))
		}
	}else{
		ret = &mymongo.Retdate{"PostInsert", 0, "上传频繁，请休息会"}
	}

	log.Println("MyInsert",ret)
	cont.JSON(StatusOK, gin.H{"code":ret.Code, "msg":ret.Mesg, "call":ret.Type})
}


func MyUpdate(cont *gin.Context, client *mymongo.Myclient, methods string) {
	var ret *mymongo.Retdate
	id := cont.PostForm("id")
	file, _ := cont.FormFile("file")
	if id == "" {
		ret = &mymongo.Retdate{"MyUpdate", 0, "参数不正确"}
	}else{
		res, retd := client.FindOne(id)
		if retd.Code == 1 {
			_type, suffx := myfiles.FileSuffix(file.Filename)
			_time := strings.Split(time.Now().String(), ".")[0]
			ret = client.Update(file.Filename, _time, id, _type, suffx)
			if ret.Code == 1 {
				os.Remove(filepath.Join(MPATH, res.Id, res.Name))
				cont.SaveUploadedFile(file, filepath.Join(MPATH, res.Id, file.Filename))
			}
		}else{
			ret = &mymongo.Retdate{"MyUpdate", 0, "id不存在"}
		}
	}
	
	log.Println("MyUpdate", ret)
	cont.JSON(StatusOK, gin.H{"code":ret.Code, "msg":ret.Mesg, "call":ret.Type})
}


func MyDelete(cont *gin.Context, client *mymongo.Myclient, methods string) {
	var dict Mydata
	var ret  *mymongo.Retdate

	switch methods {
		case "GET":
			dict.Id = cont.DefaultQuery("id", "")
		case "POST":
			mbody, _ := ioutil.ReadAll(cont.Request.Body)
			json.Unmarshal(mbody, &dict)
	}

	if dict.Id == "" {
		ret = &mymongo.Retdate{"MyDelete", 0, "参数不正确"}
	}else{
		ret = client.Delete(dict.Id)
		if ret.Code == 1 {
			os.RemoveAll(filepath.Join(MPATH, dict.Id))
		}
	}

	log.Println("MyDelete", ret)
	cont.JSON(StatusOK, gin.H{"code":ret.Code, "msg":ret.Mesg, "call":ret.Type})
}

func Myrefresh(cont *gin.Context, client *mymongo.Myclient, methods string) {
	sum1 := client.FindSeekAll()
	sum2 := client.Count
	client.Count = sum1
	cont.JSON(StatusOK, gin.H{"new": sum1, "original": sum2, "update": sum1-sum2})
}

func MyFindAll(cont *gin.Context, client *mymongo.Myclient, methods string) {
	var body Mypage
	
	switch methods {
		case "GET":
			body.Limit, _ = strconv.ParseInt(cont.DefaultQuery("limit", "20"), 10, 64)
			body.Page,  _ = strconv.ParseInt(cont.DefaultQuery("page", "0"), 10, 64)
			body.Search   = cont.DefaultQuery("search", "")
		case "POST":
			mbody, _ := ioutil.ReadAll(cont.Request.Body)
			json.Unmarshal(mbody, &body)
	}
	Pagesum := client.Count
	if body.Search != "" {
		Pagesum = client.FindSeekSearch(body.Search)
	}
	res, ret := client.FindAll(body.Limit, body.Page, body.Search)
	_page := Pagesum/body.Limit
	if Pagesum%body.Limit != 0 {
		_page++
	}

	log.Println("MyFindAll",ret)
	cont.JSON(StatusOK, gin.H{"code":ret.Code, "msg":res, "call":ret.Mesg, "page": _page})
}


func MyFindOne(cont *gin.Context, client *mymongo.Myclient, methods string) {
	var dict Mydata
	
	switch methods {
		case "GET":
			dict.Id = cont.DefaultQuery("id", "")
		case "POST":
			mbody, _ := ioutil.ReadAll(cont.Request.Body)
			json.Unmarshal(mbody, &dict)
	}
	
	res, ret := client.FindOne(dict.Id)
	log.Println("MyFindOne", ret)

	cont.JSON(StatusOK, gin.H{"code":ret.Code, "msg":res, "call":ret.Mesg})
}


func MyDownload(cont *gin.Context, client *mymongo.Myclient, methods string) {
	var dict Mydata

	switch methods {
		case "GET":
			dict.Id   = cont.DefaultQuery("id", "")
			dict.Name = cont.DefaultQuery("name", "")
		case "POST":
			mbody, _ := ioutil.ReadAll(cont.Request.Body)
			json.Unmarshal(mbody, &dict)
	}
	file_Path := filepath.Join(MPATH, dict.Id, dict.Name)
  	cont.Header("Content-Type", "application/octet-stream")
  	cont.Header("Content-Disposition", "attachment; filename=" + dict.Name)
  	cont.Header("Content-Transfer-Encoding", "binary")
  	cont.Header("Cache-Control", "no-cache")
  	if myfiles.Exists(file_Path) {
      	cont.File(file_Path)
  	}else{
	  	cont.Redirect(StatusNotFound, "/404")
  	}
}

// 封面接口
func MyCoverv3(cont *gin.Context, client *mymongo.Myclient, methods string) {

	switch methods {
		case "GET": 
			Id := cont.DefaultQuery("id", "")
			resp, _ := client.FindOne(Id)
			mdir := filepath.Join(MPATH, Id, resp.Image)
			cont.Header("Content-Type", "application/octet-stream")
			cont.Header("Content-Transfer-Encoding", "binary")
			cont.Header("Cache-Control", "no-cache")
			if myfiles.Exists(mdir) && myfiles.IsFile(mdir) {
				cont.Header("Content-Disposition", "attachment; filename=" + resp.Image)
				cont.File(mdir)
			}else{
				cont.Redirect(StatusMovedPermanently, "/images/demo.jpg")
			}
		case "POST":
			var code = int64(0)
			file, _ := cont.FormFile("file")
			id := cont.PostForm("id")
			mdir := filepath.Join(MPATH, id)
			client.Update_Coverv(id, file.Filename)
			if myfiles.IsDir(mdir) {
				cont.SaveUploadedFile(file, filepath.Join(MPATH, id, file.Filename))
				code = 1
			}
			cont.JSON(StatusOK, gin.H{"code": code, "data": mdir})
	}

}

// 字幕歌词接口
func MySubtitle_lyrics(cont *gin.Context, client *mymongo.Myclient, methods string) {

	switch methods {
		case "GET": 
			Id := cont.DefaultQuery("id", "")
			resp, _ := client.FindOne(Id)
			var file string
			switch resp.Type {
				case "1":
					file = "vtt.vtt"
				case "2":
					file = "lrc.lrc"
				default:
					file = "none"
			}
			mdir := filepath.Join(MPATH, Id, file)
			if myfiles.Exists(mdir) {
				cont.Header("Content-Type", "application/octet-stream")
				cont.Header("Content-Disposition", "attachment; filename=" + file)
				cont.Header("Content-Transfer-Encoding", "binary")
				cont.Header("Cache-Control", "no-cache")
				cont.File(mdir)
			}else{
				cont.Redirect(StatusNotFound, "/404")
			}
		case "POST":
			var code = int64(0)
			file, _ := cont.FormFile("file")
			id := cont.PostForm("id")
			mdir := filepath.Join(MPATH, id)
			_, sux := myfiles.FileSuffix(file.Filename)
			if myfiles.IsDir(mdir) {
				cont.SaveUploadedFile(file, filepath.Join(MPATH, id, sux + "." + sux))
				code = 1
			}
			cont.JSON(StatusOK, gin.H{"code": code, "data": mdir})
	}
}

//上传弹幕评论文件接口
func MyUpdateBarrage_comments(cont *gin.Context, client *mymongo.Myclient, methods string) {
	var code = int64(0)
	file, _ := cont.FormFile("file")
	id := cont.PostForm("id")
	mdir := filepath.Join(MPATH, id)
	resp, _ := client.FindOne(id)
	if myfiles.IsDir(mdir) && resp.Id == id {
		file_read, err := file.Open()
		if err == nil {
			content, _ := ioutil.ReadAll(file_read)
			re := regexp.MustCompile(`p="(.*?)">(.*?)</d><d`)
			bytest := re.FindAllSubmatch(content, -1)
			var d mymongo.Barrage_comments
			for _,v := range bytest {
			  	t := strings.Split(string(v[1]), ",")
			   	var st []interface{}
			   	st = append(st, myfiles.Tofloat64(t[0]))
			   	st = append(st, myfiles.Toint64(t[5]))
			   	st = append(st, myfiles.Toint64(t[3]))
			   	st = append(st, t[6])
			   	st = append(st, string(v[2]))
			   	d.Data = append(d.Data, st)
			}
			d.Id = resp.Id
			res := client.Upd_barrage(&d)
			code = res.Code
		}
		file_read.Close()
	}
	cont.JSON(StatusOK, gin.H{"code": code})
}

// 弹幕评论接口
func MyBarrage_comments(cont *gin.Context, client *mymongo.Myclient, methods string) {
	var code = int64(0)
	switch methods {
		case "GET":
			var res *mymongo.Barrage_comments
			Id := cont.DefaultQuery("id", "")
			resp, _ := client.FindOne(Id)
			switch resp.Type {
				case "1":
					res, _ = client.Get_barrage(Id)
				case "2":
					res, _ = client.Get_comment(Id)
				default:
					code = 1
			}
			log.Println("MyBarrage_comments", resp)
			cont.JSON(StatusOK, gin.H{"code": code, "data":res.Data})
		case "POST":
			var dict *mymongo.MyBarrage
			var res *mymongo.Retdate
			jsonData, _ := ioutil.ReadAll(cont.Request.Body)
			json.Unmarshal(jsonData, &dict)
			resp, _ := client.FindOne(dict.Id)
			switch resp.Type {
				case "1":
					res = client.Add_barrage(dict)
				case "2":
					res = client.Add_comment(dict)
				default:
					code = 1
			}
			log.Println("MyBarrage_comments", res)
			cont.JSON(StatusOK, gin.H{"code": code, "data": res})
	}
	
}


//ffmpeg 接口
func Myffmpeg_conversion(cont *gin.Context, client *mymongo.Myclient, methods string) {
	switch methods {
		case "GET":
			//升级ws协议 查看转码进度条
			ffmpeg.Seek_ffmpeg(cont)
		case "POST":
			id := cont.PostForm("id")
			resp, _ := client.FindOne(id)
			if !ffmpeg.Isrun(resp.Id) {
				call := func() {
					var suffx string
					switch resp.Type {
						case "1":
							suffx = "mp4"
						case "2":
							suffx = "mp3"
					}
					name := strings.TrimSuffix(resp.Name, filepath.Ext(resp.Name)) + "." + suffx
					client.Update(name, resp.Time, resp.Id, resp.Type, suffx)
				}
				switch resp.Type {
					case "1":
						ffmpeg.Addffmpeg_mp4(resp.Id, resp.Name, MPATH, call)
					case "2":
						ffmpeg.Addffmpeg_mp3(resp.Id, resp.Name, MPATH, call)
				}
				cont.JSON(StatusOK, gin.H{"code": 1, "data": "添加成功，ws访问进度"})
			}else{
				cont.JSON(StatusOK, gin.H{"code": 1, "data": "已在运行，ws访问进度"})
			}
			
	}
	
}


func MyUser(cont *gin.Context, client *mymongo.Myclient, methods string) {
	log.Println(cont.ClientIP())
	model := cont.DefaultQuery("model", "")
	switch model {
		case "get-user":
		case "new-user":
	}
	cont.JSON(StatusOK, gin.H{"code":1, "msg":"admin", "call":"MyUser"})
}



func Authorize() gin.HandlerFunc {
  return func(c *gin.Context) {
        method := c.Request.Method
        origin := c.Request.Header.Get("Origin") //请求头部
        if origin != "" {
            //接收客户端发送的origin （重要！）
            c.Writer.Header().Set("Access-Control-Allow-Origin", origin) 
            //服务器支持的所有跨域请求的方法
            c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE") 
            //允许跨域设置可以返回其他子段，可以自定义字段
            c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session")
            // 允许浏览器（客户端）可以解析的头部 （重要）
            c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers") 
            //设置缓存时间
            c.Header("Access-Control-Max-Age", "172800") 
            //允许客户端传递校验信息比如 cookie (重要)
            c.Header("Access-Control-Allow-Credentials", "true")                                                                                                                                                                                                                          
        }

        //允许类型校验 
        if method == "OPTIONS" {
			c.Header("Access-Control-Allow-Origin", "*")
            c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization") //自定义 Header
            c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
            c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
            c.Header("Access-Control-Allow-Credentials", "true")
            c.AbortWithStatus(StatusOK)
        }

        defer func() {
            if err := recover(); err != nil {
                log.Printf("Panic info is: %v", err)
            }
        }()
        c.Next()
    }
}
